package auth

import (
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"strings"
	"sync"

	"github.com/fsnotify/fsnotify"
)

type Authenticator struct {
	Password map[string]string
	stop     chan interface{}
	mu       *sync.RWMutex
}

func NewAuthenticator(path string) (*Authenticator, error) {
	auth := Authenticator{
		Password: make(map[string]string),
		stop:     make(chan interface{}),
		mu:       &sync.RWMutex{},
	}
	if err := auth.LoadPassword(path); err != nil {
		return nil, err
	}
	if err := auth.FileMonitor(path); err != nil {
		return nil, err
	}
	return &auth, nil
}

func (a *Authenticator) Close() {
	close(a.stop)
}

func (a *Authenticator) Auth(appId, timestamp, signature string) error {
	password := a.Password[appId]
	if password == "" {
		return fmt.Errorf("Invalid appId")
	}
	text := appId + password + timestamp
	md5Hash := md5.New()
	md5Hash.Write([]byte(text))
	cipher := md5Hash.Sum(nil)
	c := hex.EncodeToString(cipher)
	if strings.ToLower(c) != strings.ToLower(signature) {
		return fmt.Errorf("Wrong signature")
	}
	return nil
}

func (a *Authenticator) LoadPassword(path string) error {
	password := make(map[string]string)
	data, err := ioutil.ReadFile(path+"/p.json")
	if err != nil {
		return fmt.Errorf("auth.LoadPassword: Failed to read password configure: %v", err)
	}
	err = json.Unmarshal(data, &password)
	if err != nil {
		return fmt.Errorf("auth.LoadPassword: Failed to parse password configure: %v", err)
	}
	a.Password = password
	return nil
}

func (a *Authenticator) FileMonitor(path string) error {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return fmt.Errorf("auth.FileMonitor: Failed to new file watcher: %v", err)
	}
	defer watcher.Close()
	if err = watcher.Add(path); err != nil {
		return fmt.Errorf("auth.FileMonitor: Failed to add file watcher: %v", err)
	}

	go func() {
		for {
			select {
			case event := <-watcher.Events:
				if event.Name == "p.json" && event.Op == fsnotify.Write {
					a.mu.RLock()
					defer a.mu.RUnlock()
					a.LoadPassword(path)
				}
			case <-a.stop:
				return
			}
		}
	}()
	return nil
}
